package app.config;

import app.web.admin.domain.pojo.Admin;
import app.web.admin.domain.pojo.AdminMenu;
import app.web.admin.domain.pojo.AdminRole;
import app.web.admin.mapper.AdminMapper;
import app.web.admin.mapper.AdminMenuMapper;
import app.web.admin.mapper.AdminRoleMapper;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import com.gitee.feizns.quickstart.dict.spring.boot.autoconfigure.QkDictBeanNames;
import com.gitee.feizns.quickstart.web.Interceptors.bucket4j.CaffeineRateLimitInterceptor;
import com.gitee.feizns.quickstart.web.Interceptors.bucket4j.handler.Qps;
import com.gitee.feizns.quickstart.web.advice.crypto.CryptoRequestResponseBodyAdvice;
import com.gitee.feizns.quickstart.web.decrypt.DecryptionAlgorithmPrivateKeySupplier;
import com.gitee.feizns.quickstart.web.spring.boot.autoconfigure.decrypt.DecryptAutoConfiguration;
import io.github.bucket4j.Bucket;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 全局配置
 * @author feizns
 * @since 2025/4/9
 */
@Configuration
@DependsOn(QkDictBeanNames.Depositor)
public class GlobalConfiguration implements ApplicationRunner, WebMvcConfigurer, ErrorPageRegistrar {

    /**
     * 加密请求响应
     * @return {@link CryptoRequestResponseBodyAdvice }
     */
    @Bean
    public CryptoRequestResponseBodyAdvice cryptoRequestResponseBodyAdvice() {
        return new CryptoRequestResponseBodyAdvice(){
            @Override
            public List<MediaType> getMediaTypes() {
                return Config.cryptoMediaTypes();
            }

            @Override
            public List<String> getPatterns() {
                return Config.cryptoUrlPatterns();
            }
        };
    }

    /**
     * RSA 密钥重置调度程序
     * @return {@link SchedulingConfigurer }
     */
    @Bean
    public SchedulingConfigurer rsaKeysResetScheduler() {
        return registrar -> {
            String cron = Config.rsaKeysResetIntervalCron();
            registrar.addCronTask(Config::resetRsaKeys, cron);
        };
    }

    @Override
    public void addInterceptors(@NonNull InterceptorRegistry registry) {
        registry.addInterceptor(new CaffeineRateLimitInterceptor(key -> Bucket.builder().addLimit(Qps.qps(Config.qpsLimit())).build()){
            @Override
            public List<String> getUrlPatterns() {
                return Config.qpsPatterns();
            }
        }).addPathPatterns("/**");
        // 注册 Sa-Token 拦截器，校验规则为 StpUtil.checkLogin() 登录校验。
        Config.saTokenExcludePathPatterns();
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns(Config.saTokenExcludePathPatterns());
    }

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/index.html"));
    }

    /**
     * RSA 私钥供应商
     * @see DecryptAutoConfiguration
     * @return {@link DecryptionAlgorithmPrivateKeySupplier }
     */
    @Bean
    public DecryptionAlgorithmPrivateKeySupplier rsaPrivateKeySupplier() {
        return Config::rsaPrivateKey;
    }

    @Override
    public void run(ApplicationArguments args) {
        //基础配置初始化
        Config.init();
        //初始化超级管理员
        Admin admin = initSuperAdmin();
    }

    /**
     * 初始化超级管理员
     */
    public static Admin initSuperAdmin() {
        //插入超级权限
        AdminMenu menu = insertSuperMenu();
        //插入超级管理员角色
        List<Long> roleMenuIds = Collections.singletonList(menu.getId());
        AdminRole role = insertSuperRole(roleMenuIds);
        //插入超级管理员
        List<Long> roleIds = Collections.singletonList(role.getId());
        return insertSuperAdmin(roleIds);
    }

    /**
     * 插入超级管理员
     */
    public static Admin insertSuperAdmin(List<Long> roleIds) {
        Admin admin = AdminMapper.superAdmin();
        if ( admin == null ) {
            admin = new Admin();
            admin.setId(Admin.SuperAdminId);
            admin.setName("超级管理员");
            admin.setMobile("mx-info");
            admin.setAvatar(Config.defaultAvatarUrl());
            admin.setTitle("超级管理员");
            admin.setPassword(Config.passwordEncoder().encode("OOoo0000"));
            admin.setRoleIds(roleIds);
            admin.save();
            admin.relationshipOfAdminOfRole().add(roleIds);
        }
        return admin;
    }

    /**
     * 插入超级角色
     */
    public static AdminRole insertSuperRole(List<Long> menuIds) {
        AdminRole role = AdminRoleMapper.superAdminRole();
        if ( role == null ) {
            role = new AdminRole();
            role.setId(AdminRole.SuperAdminRoleId);
            role.setName("超级管理员");
            role.setGroupName("系统");
            role.setMenuIds(menuIds);
            role.save();
            role.relationshipOfRoleMenus().add(menuIds);
        }
        return role;
    }

    /**
     * 插入超级权限
     */
    public static AdminMenu insertSuperMenu() {
        AdminMenu permission = AdminMenuMapper.superMenu();
        if ( permission == null ) {
            permission = new AdminMenu();
            permission.setId(AdminMenu.SuperAdminRolePermissionId);
            permission.setName("所有菜单");
            permission.setIdentifier("*");
            permission.setExtra(Map.of());
            permission.save();
        }
        return permission;
    }

}
